/*
 * NewTableDialog.cpp
 *
 * Copyright 2012 Pieter Agten
 *
 * This file is part of tltgen.
 *
 * tltgen is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * tltgen is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with tltgen.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @file   NewTableDialog.cpp
 * @author Pieter Agten (pieter.agten@gmail.com)
 * @date   4 sep 2012
 */

#include <wx/wx.h>
#include <wx/spinctrl.h>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>

#include "BetaModel.hpp"
#include "NormalizedSteinhartHartModel.hpp"
#include "CircuitPanel.hpp"
#include "TemperatureScale.hpp"
#include "ThermistorTable.hpp"
#include "ThermistorTableRow.hpp"

#include "NewTableDialog.hpp"

using boost::shared_ptr;
using boost::make_shared;

namespace tltgen {

  BEGIN_EVENT_TABLE(NewTableDialog, wxDialog)
  EVT_RADIOBUTTON(ID_RadEmpty, NewTableDialog::onTableSourceChanged)
  EVT_RADIOBUTTON(ID_RadBeta, NewTableDialog::onTableSourceChanged)
  EVT_RADIOBUTTON(ID_RadShh, NewTableDialog::onTableSourceChanged)
  END_EVENT_TABLE()

  const wxString NewTableDialog::INTEGER_CHARACTERS[] = {_("0"), _("1"), _("2"), _("3"), _("4"), _("5"), _("6"), _("7"), _("8"), _("9")};

  NewTableDialog::NewTableDialog(wxWindow* parent, wxWindowID id, const shared_ptr<TemperatureScale>& temperatureScale, const wxString& title, 
				 const wxPoint& pos, const wxSize& size, long style ) :
    wxDialog(parent, id, title, pos, size, style),
    temperatureScale(temperatureScale)
  {
    initializeComponents();

    Centre();
  }


  void NewTableDialog::initializeComponents()
  {
    wxSizer* vbox = new wxBoxSizer(wxVERTICAL);

    vbox->Add(createTableSettingsPanel(this));

    this->circuitPanel = new CircuitPanel(this, wxID_ANY);
    vbox->Add(this->circuitPanel, 0, wxALL, 15);

    // OK/Cancel buttons
    wxSizer *buttonSizer = CreateSeparatedButtonSizer(wxOK | wxCANCEL);
    vbox->Add(buttonSizer, 0, wxALIGN_RIGHT | wxRIGHT | wxBOTTOM, 12);

    SetSizer(vbox);
    vbox->SetSizeHints(this);
  }

  wxPanel* NewTableDialog::createTableSettingsPanel(wxWindow* parent)
  {
    wxPanel* panel = new wxPanel(this);
    wxSizer* vbox = new wxBoxSizer(wxVERTICAL);

    vbox->Add(createTableSourcePanel(panel), 0, wxALL, 10);
    vbox->Add(createTableSizePanel(panel), 0, wxALL, 5);

    updateTableSourcePanelsEnabled();

    panel->SetSizer(vbox);
    return panel;
  }

  wxPanel* NewTableDialog::createTableSourcePanel(wxWindow* parent)
  {
    wxPanel* panel = new wxPanel(parent, wxID_ANY);
    wxSizer* vbox = new wxBoxSizer(wxVERTICAL);

    // Empty table radio button
    this->radEmptyTable = new wxRadioButton(panel, ID_RadBeta, wxT("Empty table"), wxDefaultPosition, wxDefaultSize, wxRB_SINGLE);
    this->radEmptyTable->SetValue(true);
    vbox->Add(this->radEmptyTable);

    // Beta model radio button
    this->radBetaGen = new wxRadioButton(panel, ID_RadBeta, wxT("From Beta model"));
    this->radBetaGen->SetValue(false);
    vbox->Add(this->radBetaGen, 0, wxTOP, 5);
    vbox->Add(createBetaPanel(panel));    

    // Shh model radio button
    this->radShhGen = new wxRadioButton(panel, ID_RadShh, wxT("From Steinhart-Hart model"));
    this->radShhGen->SetValue(false);
    vbox->Add(this->radShhGen, 0, wxTOP, 5);
    vbox->Add(createShhPanel(panel));

    panel->SetSizer(vbox);
    return panel;
  }


  wxPanel* NewTableDialog::createBetaPanel(wxWindow* parent)
  {
    wxPanel* panel = new wxPanel(parent, wxID_ANY);
    wxBoxSizer* hbox = new wxBoxSizer(wxHORIZONTAL);
    
    hbox->Add(new wxStaticText(panel, wxID_ANY, _("T0")), 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 20);
    this->txtBetaT0 = new wxTextCtrl(panel, wxID_ANY, formatTemperature(273.15 + 25), wxDefaultPosition, wxSize(100, wxDefaultSize.GetHeight()), 0, wxTextValidator(wxFILTER_NUMERIC));
    hbox->Add(this->txtBetaT0, 0, wxLEFT, 2);

    hbox->Add(new wxStaticText(panel, wxID_ANY, _("R0")), 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 10);
    this->txtBetaR0 = new wxTextCtrl(panel, wxID_ANY, formatResistance(100000), wxDefaultPosition, wxSize(100, wxDefaultSize.GetHeight()), 0, wxTextValidator(wxFILTER_NUMERIC));
    hbox->Add(this->txtBetaR0, 0, wxLEFT, 2);

    hbox->Add(new wxStaticText(panel, wxID_ANY, _("Beta")), 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 10);
    this->txtBetaBeta = new wxTextCtrl(panel, wxID_ANY, formatBetaValue(4066), wxDefaultPosition, wxSize(100, wxDefaultSize.GetHeight()), 0, wxTextValidator(wxFILTER_NUMERIC));
    hbox->Add(this->txtBetaBeta, 0, wxLEFT, 2);

    panel->SetSizer(hbox);
    return panel;
  }

  wxPanel* NewTableDialog::createShhPanel(wxWindow* parent)
  {
    wxPanel* panel = new wxPanel(parent, wxID_ANY);
    wxBoxSizer* hbox = new wxBoxSizer(wxHORIZONTAL);
    
    hbox->Add(new wxStaticText(panel, wxID_ANY, _("R0")), 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 20);
    this->txtShhR0 = new wxTextCtrl(panel, wxID_ANY, formatResistance(100000), wxDefaultPosition, wxSize(100, wxDefaultSize.GetHeight()), 0, wxTextValidator(wxFILTER_NUMERIC));
    hbox->Add(this->txtShhR0, 0, wxLEFT, 2);

    //TODO: positive
    hbox->Add(new wxStaticText(panel, wxID_ANY, _("A")), 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 10);
    this->txtShhA = new wxTextCtrl(panel, wxID_ANY, formatShhParameter(1/(273.15 + 25.0)), wxDefaultPosition, wxSize(100, wxDefaultSize.GetHeight()), 0, wxTextValidator(wxFILTER_NUMERIC));
    hbox->Add(this->txtShhA, 0, wxLEFT, 2);

    //TODO: positive
    hbox->Add(new wxStaticText(panel, wxID_ANY, _("B")), 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 10);
    this->txtShhB = new wxTextCtrl(panel, wxID_ANY, formatShhParameter(1E-4), wxDefaultPosition, wxSize(100, wxDefaultSize.GetHeight()), 0, wxTextValidator(wxFILTER_NUMERIC));
    hbox->Add(this->txtShhB, 0, wxLEFT, 2);

    hbox->Add(new wxStaticText(panel, wxID_ANY, _("C")), 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 10);
    this->txtShhC = new wxTextCtrl(panel, wxID_ANY, formatShhParameter(0), wxDefaultPosition, wxSize(100, wxDefaultSize.GetHeight()), 0, wxTextValidator(wxFILTER_NUMERIC));
    hbox->Add(this->txtShhC, 0, wxLEFT, 2);

    hbox->Add(new wxStaticText(panel, wxID_ANY, _("D")), 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 10);
    this->txtShhD = new wxTextCtrl(panel, wxID_ANY, formatShhParameter(4E-14), wxDefaultPosition, wxSize(100, wxDefaultSize.GetHeight()), 0, wxTextValidator(wxFILTER_NUMERIC));
    hbox->Add(this->txtShhD, 0, wxLEFT, 2);

    panel->SetSizer(hbox);
    return panel;
  }

  wxPanel* NewTableDialog::createTableSizePanel(wxWindow* parent)
  {
    wxTextValidator integerValidator(wxFILTER_INCLUDE_CHAR_LIST);
    integerValidator.SetIncludes(wxArrayString(10, NewTableDialog::INTEGER_CHARACTERS));

    wxPanel* panel = new wxPanel(parent, -1);
    wxSizer* hbox = new wxBoxSizer(wxHORIZONTAL);
    
    wxStaticText* stNbRows = new wxStaticText(panel, -1, _("Nb. rows"));
    wxStaticText* stTempRange = new wxStaticText(panel, -1, _("Temp. range"));
    wxStaticText* stDash = new wxStaticText(panel, -1, _("-"));

    this->spinNbRows = new wxSpinCtrl(panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(70, wxDefaultSize.GetHeight()));
    this->spinNbRows->SetRange(2, 9999);
    this->spinNbRows->SetValue(61);
    this->txtFromTemp = new wxTextCtrl(panel, wxID_ANY, formatTemperature(273.15), wxDefaultPosition, wxSize(70, wxDefaultSize.GetHeight()), 0, wxTextValidator(wxFILTER_NUMERIC));
    this->txtToTemp = new wxTextCtrl(panel, wxID_ANY, formatTemperature(273.15 + 300), wxDefaultPosition, wxSize(70, wxDefaultSize.GetHeight()), 0, wxTextValidator(wxFILTER_NUMERIC));

    hbox->Add(stNbRows, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 10);
    hbox->Add(this->spinNbRows, 0, wxLEFT, 2);
    hbox->Add(stTempRange, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 10);
    hbox->Add(this->txtFromTemp, 0, wxLEFT, 2);
    hbox->Add(stDash, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, 3);
    hbox->Add(this->txtToTemp);

    panel->SetSizer(hbox);
    return panel;
  }

  void NewTableDialog::onTableSourceChanged(wxCommandEvent &event)
  {
    updateTableSourcePanelsEnabled();
  }

  void NewTableDialog::updateTableSourcePanelsEnabled()
  {
    if (this->radEmptyTable->GetValue()) {
      setBetaPanelEnabled(false);
      setShhPanelEnabled(false);
      setTableSizePanelEnabled(false);
    } else if (this->radBetaGen->GetValue()) {
      setBetaPanelEnabled(true);
      setShhPanelEnabled(false);
      setTableSizePanelEnabled(true);
    } else if (this->radShhGen->GetValue()) {
      setBetaPanelEnabled(false);
      setShhPanelEnabled(true);
      setTableSizePanelEnabled(true);
    }
  }

  wxString NewTableDialog::formatTemperature(Temperature t) const
  {
    return wxString::Format(wxT("%.1f"), getTemperatureScale()->fromKelvin(t));
  }

  wxString NewTableDialog::formatResistance(Resistance r) const
  {
    return wxString::Format(wxT("%.0f"), r);
  }

  wxString NewTableDialog::formatBetaValue(double b) const
  {
    return wxString::Format(wxT("%.0f"), b);
  }

  wxString NewTableDialog::formatShhParameter(double v) const
  {
    return wxString::Format(wxT("%.6g"), v);
  }

  double NewTableDialog::getValueAsFloat(const wxString& string) const
  {
    double value = NAN;
    string.ToDouble(&value);
    return value;
  }

  /*int NewTableDialog::getValueAsInt(const wxString& string) const
  {
    long value = 0;
    string.ToLong(&value);
    return (int)value;
    }*/

  void NewTableDialog::setBetaValues(const BetaModelThermistor* thermistor) const
  {
    this->txtBetaT0->SetValue(formatTemperature(thermistor->getT0()));
    this->txtBetaR0->SetValue(formatResistance(thermistor->getR0()));
    this->txtBetaBeta->SetValue(formatBetaValue(thermistor->getBeta()));
  }
  void NewTableDialog::setShhValues(const NormalizedSteinhartHartModelThermistor* thermistor) const
  {
    this->txtShhR0->SetValue(formatResistance(thermistor->getR0()));
    this->txtShhA->SetValue(formatShhParameter(thermistor->getA()));
    this->txtShhB->SetValue(formatShhParameter(thermistor->getB()));
    this->txtShhC->SetValue(formatShhParameter(thermistor->getC()));
    this->txtShhD->SetValue(formatShhParameter(thermistor->getD()));
  }

  bool NewTableDialog::isEmptyTableSelected() const
  {
    return this->radEmptyTable->GetValue();
  }
  bool NewTableDialog::isBetaModelTableSelected() const
  {
    return this->radBetaGen->GetValue();
  }
  bool NewTableDialog::isShhModelTableSelected() const
  {
    return this->radShhGen->GetValue();
  }

  Resistance NewTableDialog::getBetaModelT0() const
  {
    return getValueAsFloat(this->txtBetaT0->GetValue());
  }
  Resistance NewTableDialog::getBetaModelR0() const
  {
    return getValueAsFloat(this->txtBetaR0->GetValue());
  }
  double NewTableDialog::getBetaModelBeta() const
  {
    return getValueAsFloat(this->txtBetaBeta->GetValue());
  }
  void NewTableDialog::setBetaPanelEnabled(bool value)
  {
    this->txtBetaT0->Enable(value);
    this->txtBetaR0->Enable(value);
    this->txtBetaBeta->Enable(value);
  }

  
  Resistance NewTableDialog::getShhModelR0() const
  {
    return getValueAsFloat(this->txtShhR0->GetValue());
  }
  double NewTableDialog::getShhModelA() const
  {
    return getValueAsFloat(this->txtShhA->GetValue());
  }
  double NewTableDialog::getShhModelB() const
  {
    return getValueAsFloat(this->txtShhB->GetValue());
  }
  double NewTableDialog::getShhModelC() const
  {
    return getValueAsFloat(this->txtShhC->GetValue());
  }
  double NewTableDialog::getShhModelD() const
  {
    return getValueAsFloat(this->txtShhD->GetValue());
  }
  void NewTableDialog::setShhPanelEnabled(bool value)
  {
    this->txtShhR0->Enable(value);
    this->txtShhA->Enable(value);
    this->txtShhB->Enable(value);
    this->txtShhC->Enable(value);
    this->txtShhD->Enable(value);
  }

  int NewTableDialog::getNbTableRows() const
  {
    return this->spinNbRows->GetValue();
  }
  Temperature NewTableDialog::getLowestTemperature() const
  {
    return getValueAsFloat(this->txtFromTemp->GetValue());
  }
  Temperature NewTableDialog::getHighestTemperature() const
  {
    return getValueAsFloat(this->txtToTemp->GetValue());
  }
  void NewTableDialog::setTableSizePanelEnabled(bool value)
  {
    this->spinNbRows->Enable(value);
    this->txtFromTemp->Enable(value);
    this->txtToTemp->Enable(value);
  }


  shared_ptr<ThermistorTable> NewTableDialog::createThermistorTable() const
  {
    shared_ptr<ThermistorTable> table = make_shared<ThermistorTable>();
    table->setCircuit(this->circuitPanel->createCircuit());
    table->setAnalogToDigitalConverter(this->circuitPanel->createAnalogToDigitalConverter());

    shared_ptr<Thermistor> thermistor = createThermistor();
    if (thermistor != NULL) {
      fillThermistorTable(table, thermistor);
    }
    return table;
  }

  shared_ptr<Thermistor> NewTableDialog::createThermistor() const
  {
    if (isBetaModelTableSelected()) {
      Temperature kelvin_t0 = getTemperatureScale()->toKelvin(getBetaModelT0());
      return make_shared<BetaModelThermistor>(kelvin_t0, getBetaModelR0(), getBetaModelBeta());
    }

    if (isShhModelTableSelected()) {
      return make_shared<NormalizedSteinhartHartModelThermistor>(getShhModelR0(), getShhModelA(), getShhModelB(), getShhModelC(), getShhModelD());
    }

    return shared_ptr<Thermistor>();
  }

  void NewTableDialog::fillThermistorTable(const shared_ptr<ThermistorTable>& thermistorTable, const shared_ptr<Thermistor>& thermistor) const
  {
    thermistorTable->beginBatch();
    const Temperature increment = (getHighestTemperature() - getLowestTemperature())/(getNbTableRows() - 1);
    for (int i = 0; i < getNbTableRows(); ++i) {
      const Temperature t = getLowestTemperature() + increment * i;
      const Temperature kelvin_t = getTemperatureScale()->toKelvin(t);
      const Resistance resistance = thermistor->getResistance(kelvin_t);

      if (thermistorTable->isValidTemperature(kelvin_t) && thermistorTable->isValidResistance(resistance)) {
	ThermistorTableRow *row = thermistorTable->createRow(); 
	row->setTemperature(kelvin_t);
	row->setResistance(resistance);
      }
    }
    thermistorTable->endBatch();
  }

  void NewTableDialog::visit(const BetaModelThermistor* thermistor)
  {
    setBetaValues(thermistor);
  }
  void NewTableDialog::visit(const NormalizedSteinhartHartModelThermistor* thermistor)
  {
    setShhValues(thermistor);
  }
  void NewTableDialog::visit(const LinearInterpolationThermistor* thermistor)
  { }

  void NewTableDialog::setThermistorValues(const shared_ptr<Thermistor const>& thermistor)
  {
    thermistor->accept(this);
  }

  void NewTableDialog::setAdcValues(const shared_ptr<AnalogToDigitalConverter const>& adc)
  {
    this->circuitPanel->setAdcValues(adc);
  }
  void NewTableDialog::setCircuitValues(const shared_ptr<Circuit const>& circuit)
  {
    this->circuitPanel->setCircuitValues(circuit);
  }
  
  shared_ptr<TemperatureScale> NewTableDialog::getTemperatureScale() const
  {
    return this->temperatureScale;
  }

}


